{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "chat-form-dropzone", "type": "registry:component", "dependencies": [ "lucide-react" ], "devDependencies": [], "registryDependencies": [], "files": [ { "path": "./registry/components/form/file-upload/chat-form.tsx", "content": "'use client';\r\n\r\nimport {\r\n FileUploader,\r\n FileInput,\r\n FileUploaderContent,\r\n FileUploaderItem,\r\n} from '@/components/ui/file-upload';\r\nimport { Paperclip, Upload } from 'lucide-react';\r\nimport Image from 'next/image';\r\nimport { useState } from 'react';\r\nimport { DropzoneOptions } from 'react-dropzone';\r\n\r\nconst FileUploadDropzone = () => {\r\n const [files, setFiles] = useState([]);\r\n const [message, setMessage] = useState('');\r\n\r\n const dropzone = {\r\n accept: {\r\n 'image/*': ['.jpg', '.jpeg', '.png'],\r\n },\r\n multiple: true,\r\n maxFiles: 4,\r\n maxSize: 1 * 1024 * 1024,\r\n } satisfies DropzoneOptions;\r\n\r\n return (\r\n
\r\n \r\n \r\n {files?.length === 0 ? (\r\n // Layout when no files are present\r\n
\r\n \r\n \r\n Select your files\r\n \r\n
\r\n ) : (\r\n // Layout when files are present\r\n
\r\n
\r\n \r\n \r\n Select your files\r\n \r\n \r\n {files?.map((file, i) => (\r\n \r\n \r\n \r\n ))}\r\n \r\n
\r\n
\r\n )}\r\n \r\n \r\n
\r\n \r\n );\r\n};\r\n\r\nexport default FileUploadDropzone;\r\n", "type": "registry:component" }, { "path": "./components/ui/file-upload.tsx", "content": "'use client';\r\n\r\nimport { cn } from '@/lib/utils';\r\nimport {\r\n Dispatch,\r\n SetStateAction,\r\n createContext,\r\n forwardRef,\r\n useCallback,\r\n useContext,\r\n useEffect,\r\n useRef,\r\n useState,\r\n} from 'react';\r\nimport {\r\n useDropzone,\r\n DropzoneState,\r\n FileRejection,\r\n DropzoneOptions,\r\n} from 'react-dropzone';\r\nimport { toast } from 'sonner';\r\nimport { Trash2 as RemoveIcon } from 'lucide-react';\r\n\r\ntype DirectionOptions = 'rtl' | 'ltr' | undefined;\r\n\r\ntype FileUploaderContextType = {\r\n dropzoneState: DropzoneState;\r\n isLOF: boolean;\r\n isFileTooBig: boolean;\r\n removeFileFromSet: (index: number) => void;\r\n activeIndex: number;\r\n setActiveIndex: Dispatch>;\r\n orientation: 'horizontal' | 'vertical';\r\n direction: DirectionOptions;\r\n};\r\n\r\nconst FileUploaderContext = createContext(null);\r\n\r\nexport const useFileUpload = () => {\r\n const context = useContext(FileUploaderContext);\r\n if (!context) {\r\n throw new Error('useFileUpload must be used within a FileUploaderProvider');\r\n }\r\n return context;\r\n};\r\n\r\ntype FileUploaderProps = {\r\n value: File[] | null;\r\n reSelect?: boolean;\r\n onValueChange: (value: File[] | null) => void;\r\n dropzoneOptions: DropzoneOptions;\r\n orientation?: 'horizontal' | 'vertical';\r\n};\r\n\r\n/**\r\n * File upload Docs: {@link: https://localhost:3000/docs/file-upload}\r\n */\r\n\r\nexport const FileUploader = forwardRef<\r\n HTMLDivElement,\r\n FileUploaderProps & React.HTMLAttributes\r\n>(\r\n (\r\n {\r\n className,\r\n dropzoneOptions,\r\n value,\r\n onValueChange,\r\n reSelect,\r\n orientation = 'vertical',\r\n children,\r\n dir,\r\n ...props\r\n },\r\n ref\r\n ) => {\r\n const [isFileTooBig, setIsFileTooBig] = useState(false);\r\n const [isLOF, setIsLOF] = useState(false);\r\n const [activeIndex, setActiveIndex] = useState(-1);\r\n const {\r\n accept = {\r\n 'image/*': ['.jpg', '.jpeg', '.png', '.gif'],\r\n 'video/*': ['.mp4', '.MOV', '.AVI'],\r\n },\r\n maxFiles = 1,\r\n maxSize = 4 * 1024 * 1024,\r\n multiple = true,\r\n } = dropzoneOptions;\r\n\r\n const reSelectAll = maxFiles === 1 ? true : reSelect;\r\n const direction: DirectionOptions = dir === 'rtl' ? 'rtl' : 'ltr';\r\n\r\n const removeFileFromSet = useCallback(\r\n (i: number) => {\r\n if (!value) return;\r\n const newFiles = value.filter((_, index) => index !== i);\r\n onValueChange(newFiles);\r\n },\r\n [value, onValueChange]\r\n );\r\n\r\n const handleKeyDown = useCallback(\r\n (e: React.KeyboardEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n\r\n if (!value) return;\r\n\r\n const moveNext = () => {\r\n const nextIndex = activeIndex + 1;\r\n setActiveIndex(nextIndex > value.length - 1 ? 0 : nextIndex);\r\n };\r\n\r\n const movePrev = () => {\r\n const nextIndex = activeIndex - 1;\r\n setActiveIndex(nextIndex < 0 ? value.length - 1 : nextIndex);\r\n };\r\n\r\n const prevKey =\r\n orientation === 'horizontal'\r\n ? direction === 'ltr'\r\n ? 'ArrowLeft'\r\n : 'ArrowRight'\r\n : 'ArrowUp';\r\n\r\n const nextKey =\r\n orientation === 'horizontal'\r\n ? direction === 'ltr'\r\n ? 'ArrowRight'\r\n : 'ArrowLeft'\r\n : 'ArrowDown';\r\n\r\n if (e.key === nextKey) {\r\n moveNext();\r\n } else if (e.key === prevKey) {\r\n movePrev();\r\n } else if (e.key === 'Enter' || e.key === 'Space') {\r\n if (activeIndex === -1) {\r\n dropzoneState.inputRef.current?.click();\r\n }\r\n } else if (e.key === 'Delete' || e.key === 'Backspace') {\r\n if (activeIndex !== -1) {\r\n removeFileFromSet(activeIndex);\r\n if (value.length - 1 === 0) {\r\n setActiveIndex(-1);\r\n return;\r\n }\r\n movePrev();\r\n }\r\n } else if (e.key === 'Escape') {\r\n setActiveIndex(-1);\r\n }\r\n },\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [value, activeIndex, removeFileFromSet]\r\n );\r\n\r\n const onDrop = useCallback(\r\n (acceptedFiles: File[], rejectedFiles: FileRejection[]) => {\r\n const files = acceptedFiles;\r\n\r\n if (!files) {\r\n toast.error('file error , probably too big');\r\n return;\r\n }\r\n\r\n const newValues: File[] = value ? [...value] : [];\r\n\r\n if (reSelectAll) {\r\n newValues.splice(0, newValues.length);\r\n }\r\n\r\n files.forEach((file) => {\r\n if (newValues.length < maxFiles) {\r\n newValues.push(file);\r\n }\r\n });\r\n\r\n onValueChange(newValues);\r\n\r\n if (rejectedFiles.length > 0) {\r\n for (let i = 0; i < rejectedFiles.length; i++) {\r\n if (rejectedFiles[i].errors[0]?.code === 'file-too-large') {\r\n toast.error(\r\n `File is too large. Max size is ${maxSize / 1024 / 1024}MB`\r\n );\r\n break;\r\n }\r\n if (rejectedFiles[i].errors[0]?.message) {\r\n toast.error(rejectedFiles[i].errors[0].message);\r\n break;\r\n }\r\n }\r\n }\r\n },\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [reSelectAll, value]\r\n );\r\n\r\n useEffect(() => {\r\n if (!value) return;\r\n if (value.length === maxFiles) {\r\n setIsLOF(true);\r\n return;\r\n }\r\n setIsLOF(false);\r\n }, [value, maxFiles]);\r\n\r\n const opts = dropzoneOptions\r\n ? dropzoneOptions\r\n : { accept, maxFiles, maxSize, multiple };\r\n\r\n const dropzoneState = useDropzone({\r\n ...opts,\r\n onDrop,\r\n onDropRejected: () => setIsFileTooBig(true),\r\n onDropAccepted: () => setIsFileTooBig(false),\r\n });\r\n\r\n return (\r\n \r\n 0,\r\n }\r\n )}\r\n dir={dir}\r\n {...props}\r\n >\r\n {children}\r\n \r\n \r\n );\r\n }\r\n);\r\n\r\nFileUploader.displayName = 'FileUploader';\r\n\r\nexport const FileUploaderContent = forwardRef<\r\n HTMLDivElement,\r\n React.HTMLAttributes\r\n>(({ children, className, ...props }, ref) => {\r\n const { orientation } = useFileUpload();\r\n const containerRef = useRef(null);\r\n\r\n return (\r\n \r\n \r\n {children}\r\n \r\n \r\n );\r\n});\r\n\r\nFileUploaderContent.displayName = 'FileUploaderContent';\r\n\r\nexport const FileUploaderItem = forwardRef<\r\n HTMLDivElement,\r\n { index: number } & React.HTMLAttributes\r\n>(({ className, index, children, ...props }, ref) => {\r\n const { removeFileFromSet, activeIndex, direction } = useFileUpload();\r\n const isSelected = index === activeIndex;\r\n return (\r\n \r\n
\r\n {children}\r\n
\r\n removeFileFromSet(index)}\r\n >\r\n remove item {index}\r\n \r\n \r\n \r\n );\r\n});\r\n\r\nFileUploaderItem.displayName = 'FileUploaderItem';\r\n\r\ninterface FileInputProps extends React.HTMLAttributes {\r\n parentclass?: string;\r\n dropmsg?: string;\r\n}\r\nexport const FileInput = forwardRef(\r\n ({ className, parentclass, dropmsg, children, ...props }, ref) => {\r\n const { dropzoneState, isFileTooBig, isLOF } = useFileUpload();\r\n const rootProps = isLOF ? {} : dropzoneState.getRootProps();\r\n\r\n return (\r\n \r\n \r\n {children}\r\n {dropzoneState.isDragActive && (\r\n
\r\n

Drop an image here.

\r\n
\r\n )}\r\n \r\n \r\n \r\n );\r\n }\r\n);\r\n", "type": "registry:component" } ] }